//
//  AudioDownloadManager.swift
//  GhostWorld
//
//  Created by Kam on 2018/4/30.
//  Copyright © 2018 LKGood. All rights reserved.
//

import Foundation
import Reachability

private let maxDownloadingCount = 10

class AudioDownloadManager {
  
  // MARK: - Singleton
  
  static let shared = AudioDownloadManager()
  private init() { }
  
  // MARK: - Private vars
  
  private lazy var downloadManager: MZDownloadManager = { [unowned self] in
    let sessionIdentifer: String = "com.lkgood.GhostWorld.BackgroundSession"
    
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    var completion = appDelegate.downloadBackgroundSessionCompletionHandler
    
    let downloadmanager = MZDownloadManager (
      session: sessionIdentifer,
      delegate: self,
      completion: completion
    )
    return downloadmanager
    }()
  
  // MARK: - Private methods
  
  private func getUserInfo(downloadModel: MZDownloadModel, index: Int) -> [String: Any] {
    let notificationObject = DownloadNotificationObject (
      downloadModel: downloadModel,
      index: index)
    let userInfo = [DownloadNotificationObject.key: notificationObject]
    return userInfo
  }
  
  private func addToDownloaManager(audio: Audio) {
    if let audioURLString = audio.url,
      let localFileName = audio.cacheFileName,
      let cachePath = LocalFileManager.shared.cacheRootPath {
      downloadManager.addDownloadTask (localFileName, fileURL: audioURLString, destinationPath: cachePath)
    }
  }
  
  // MARK: - Public vars
  
  
  
  // MARK: - Lifecycle
  
  
  
  // MARK: - Public methods
  
  func prepare() {
    if Reachability()?.connection == .wifi {
      resumeQueue()
    }
  }
  
  func add(audio: Audio) {
    var resumeIndex = -1
    for (index, model) in downloadManager.downloadingArray.enumerated() {
      if model.fileURL == audio.url {
        resumeIndex = index
        break
      }
    }
    if resumeIndex >= 0 {
      downloadManager.resumeDownloadTaskAtIndex(resumeIndex)
    } else {
      addToDownloaManager(audio: audio)
    }
  }
  
  func pause(audio: Audio) {
    for (index, downloadModel) in downloadManager.downloadingArray.enumerated() {
      if downloadModel.fileURL == audio.url {
        downloadManager.pauseDownloadTaskAtIndex(index)
        break
      }
    }
  }
  
  func resume(audio: Audio) {
    for (index, downloadModel) in downloadManager.downloadingArray.enumerated() {
      if downloadModel.fileURL == audio.url {
        downloadManager.resumeDownloadTaskAtIndex(index)
        break
      }
    }
  }
  
  func pauseQueue() {
    for (index, _) in downloadManager.downloadingArray.enumerated() {
      downloadManager.pauseDownloadTaskAtIndex(index)
    }
  }
  
  func resumeQueue() {
    for (index, _) in downloadManager.downloadingArray.enumerated() {
      downloadManager.resumeDownloadTaskAtIndex(index)
    }
  }
  
  func remove(audio: Audio) {
    for (index, downloadModel) in downloadManager.downloadingArray.enumerated() {
      if downloadModel.fileURL == audio.url {
        downloadManager.cancelTaskAtIndex(index)
        break
      }
    }
  }
  
  func audioIsInQueue(_ audio: Audio) -> Bool {
    let downloadingURLs = downloadManager.downloadingArray.map { $0.fileURL }
    return downloadingURLs.contains(audio.url)
  }
  
  func getAllAlbumsHaveDownloadedAudios() -> [Album]? {
    let fileManager = FileManager.default
    if let cacheFolder = LocalFileManager.shared.cacheRootPath {
      do {
        let fileURLs = try fileManager.contentsOfDirectory(atPath: cacheFolder)
        let audioURLs = fileURLs.filter {
          $0.contains("ca_")
        }
        
        var userIdentifier = -1
        if let currentUser = UserManager.shared.currentUser {
          userIdentifier = currentUser.identifier
        }
        let userAudioURLs = audioURLs.filter {
          Int($0.components(separatedBy: "_")[1])! == userIdentifier
        }
        
        let albumIDs = userAudioURLs.map {
          Int($0.components(separatedBy: "_")[2])!
        }
        let albumIDSet = NSSet(array: albumIDs)
        var albums = [Album]()
        albumIDSet.forEach {
          if let album = EntityManager.fetch(Album.self, fetchType: .id($0 as! Int))?.first {
            albums.append(album)
          }
        }
        return albums
      } catch let error {
        loggingPrint("Load cache file failed. \(error.localizedDescription)")
      }
    }
    return nil
  }
  
  func getDownloadingAudiosCount() -> Int {
    return downloadManager.downloadingArray.count
  }
  
  func getDownloadingAudios() -> [Audio] {
    let audioIDs = downloadManager.downloadingArray.map {
      $0.fileName.components(separatedBy: "_").last?.components(separatedBy: ".").first
    }.filter {
      if let count = $0?.count, count > 0 {
        if let _ = Int($0!) {
          return true
        }
      }
      return false
    }.map {
      Int($0!)!
    }
    var audios = [Audio]()
    audioIDs.forEach {
      if let audio = EntityManager.fetch(Audio.self, fetchType: .id($0))?.first {
        audios.append(audio)
      }
    }
    return audios
  }
}

// MARK: - MZDownloadManagerDelegate

extension AudioDownloadManager: MZDownloadManagerDelegate {
  
  func downloadRequestStarted(_ downloadModel: MZDownloadModel, index: Int) {
    DispatchQueue.main.async {
      NotificationCenter.default.post (
        name: .downloadStarted,
        object: nil,
        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
      )
    }
  }
  
  func downloadRequestDidPopulatedInterruptedTasks(_ downloadModels: [MZDownloadModel]) {
    loggingPrint("Download request: did populated interrupted tasks")
  }
  
  func downloadRequestDidUpdateProgress(_ downloadModel: MZDownloadModel, index: Int) {
    DispatchQueue.main.async {
      NotificationCenter.default.post (
        name: .downloadDidUpdateProgress,
        object: nil,
        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
      )
    }
  }
  
  func downloadRequestDidPaused(_ downloadModel: MZDownloadModel, index: Int) {
    DispatchQueue.main.async {
      NotificationCenter.default.post (
        name: .downloadDidPaused,
        object: nil,
        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
      )
    }
  }
  
  func downloadRequestDidResumed(_ downloadModel: MZDownloadModel, index: Int) {
    DispatchQueue.main.async {
      NotificationCenter.default.post (
        name: .downloadDidResumed,
        object: nil,
        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
      )
    }
  }
  
  func downloadRequestCanceled(_ downloadModel: MZDownloadModel, index: Int) {
    DispatchQueue.main.async {
      NotificationCenter.default.post (
        name: .downloadCanceled,
        object: nil,
        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
      )
    }
  }
  
  func downloadRequestFinished(_ downloadModel: MZDownloadModel, index: Int) {
    if var downloadingAudios = DownloadingAudio.getAll() {
      // remove from core data
      var removeIndex = -1
      for (index, downloadingAudio) in downloadingAudios.enumerated() {
        if downloadingAudio.url == downloadModel.fileURL {
          removeIndex = index
          do  {
            try DownloadingAudio.remove(downloadingAudio)
          } catch { }
          break
        }
      }
      if removeIndex >= 0 {
        downloadingAudios.remove(at: removeIndex)
        if let downloadingAudio = downloadingAudios.first,
          let audio = EntityManager.fetch(Audio.self, fetchType: .id(Int(downloadingAudio.audioID)))?.first {
          addToDownloaManager(audio: audio)
        }
      }
    }
    
    DispatchQueue.main.async {
      NotificationCenter.default.post (
        name: .downloadFinished,
        object: nil,
        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
      )
    }
  }
  
  func downloadRequestDidFailedWithError(_ error: NSError, downloadModel: MZDownloadModel, index: Int) {
    loggingPrint("Download Failed. \(downloadModel.fileName ?? ""), \(downloadModel.fileURL ?? ""), \(error.localizedDescription)")
    downloadManager.cancelTaskAtIndex(index)
    downloadManager.addDownloadTask(downloadModel.fileName, fileURL: downloadModel.fileURL, destinationPath: downloadModel.destinationPath)
//    DispatchQueue.main.async {
//      NotificationCenter.default.post (
//        name: .downloadFailded,
//        object: nil,
//        userInfo: self.getUserInfo(downloadModel: downloadModel, index: index)
//      )
//    }
  }
}
